1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 This is a handy range to iterate over either all files from the user OR all 11 files in a compilation database. 12 */ 13 module compile_db.user_filerange; 14 15 import logger = std.experimental.logger; 16 import std.algorithm : map, joiner; 17 import std.array : empty, appender, array; 18 import std.range : isInputRange, ElementType, only; 19 import std.typecons : tuple, Nullable; 20 import std.exception : collectException; 21 22 import my.path : Path, AbsolutePath; 23 24 import compile_db : CompileCommand, CompileCommandFilter, CompileCommandDB, 25 parseFlag, ParseFlags, Compiler, SystemIncludePath; 26 27 @safe: 28 29 struct SimpleRange(T) { 30 private T[] values; 31 32 this(T[] values) { 33 this.values = values; 34 } 35 36 T front() { 37 assert(!empty, "Can't get front of an empty range"); 38 return values[0]; 39 } 40 41 void popFront() @safe pure nothrow @nogc { 42 assert(!empty, "Can't pop front of an empty range"); 43 values = values[1 .. $]; 44 } 45 46 bool empty() @safe pure nothrow const @nogc { 47 import std.array : empty; 48 49 return values.empty; 50 } 51 52 size_t length() @safe pure nothrow const @nogc { 53 return values.length; 54 } 55 56 typeof(this) save() @safe pure nothrow { 57 return typeof(this)(values); 58 } 59 60 static SimpleRange!T make(T[] values) { 61 return SimpleRange!T(values); 62 } 63 } 64 65 alias CompileCommandsRange = SimpleRange!CompileCommand; 66 67 /// Returns: a range over all files in the database. 68 CompileCommandsRange fileRange(CompileCommandDB db) { 69 return CompileCommandsRange(db.payload); 70 } 71 72 CompileCommandsRange fileRange(Path[] files, Compiler compiler) { 73 import std.file : getcwd; 74 75 return CompileCommandsRange(files.map!(a => CompileCommand(a, AbsolutePath(a), 76 AbsolutePath(Path(getcwd)), CompileCommand.Command([compiler]), 77 Path.init, AbsolutePath.init)).array); 78 } 79 80 /// The flags in the CompileCommand are extracted and parsed. 81 struct ParsedCompileCommand { 82 CompileCommand cmd; 83 ParseFlags flags; 84 } 85 86 alias ParsedCompileCommandRange = SimpleRange!ParsedCompileCommand; 87 88 /// Returns: a range over all files in the range where the flags have been parsed. 89 auto parse(RangeT)(RangeT r, CompileCommandFilter ccFilter) @safe nothrow 90 if (is(ElementType!RangeT == CompileCommand)) { 91 return r.map!(a => ParsedCompileCommand(a, parseFlag(a, ccFilter))); 92 } 93 94 /// Returns: a range wherein the system includes for the compiler has been 95 /// deduced and added to the `flags` data. 96 auto addSystemIncludes(RangeT)(RangeT r) @safe nothrow 97 if (is(ElementType!RangeT == ParsedCompileCommand)) { 98 static SystemIncludePath[] deduce(CompileCommand cmd, Compiler compiler) @safe nothrow { 99 import compile_db.system_compiler : deduceSystemIncludes; 100 101 try { 102 return deduceSystemIncludes(cmd, compiler); 103 } catch (Exception e) { 104 logger.info(e.msg).collectException; 105 } 106 return SystemIncludePath[].init; 107 } 108 109 return r.map!((a) { 110 a.flags.systemIncludes = deduce(a.cmd, a.flags.compiler); 111 return a; 112 }); 113 } 114 115 /// Return: add a compiler to the `flags` data if it is missing. 116 auto addCompiler(RangeT)(RangeT r, Compiler compiler) @safe nothrow 117 if (is(ElementType!RangeT == ParsedCompileCommand)) { 118 ParsedCompileCommand add(ParsedCompileCommand p, Compiler compiler) @safe nothrow { 119 if (p.flags.compiler.empty) { 120 p.flags.compiler = compiler; 121 } 122 return p; 123 } 124 125 return r.map!(a => add(a, compiler)); 126 } 127 128 /// Return: replace the compiler in `flags` with `compiler` if `compiler` is 129 /// NOT empty. 130 auto replaceCompiler(RangeT)(RangeT r, Compiler compiler) @safe nothrow 131 if (is(ElementType!RangeT == ParsedCompileCommand)) { 132 return r.map!((a) { 133 if (!compiler.empty) 134 a.flags.compiler = compiler; 135 return a; 136 }); 137 } 138 139 struct LimitFileRange { 140 /// Files not found in the compile_commands database; 141 string[] missingFiles; 142 143 ParsedCompileCommand[] commands; 144 145 bool isMissingFilesEmpty() { 146 return missingFiles.empty; 147 } 148 149 /// Returns: a range over all files that where found in the database. 150 ParsedCompileCommandRange range() { 151 return ParsedCompileCommandRange.make(commands); 152 } 153 } 154 155 /// Returns: a struct which has extracted `onlyTheseFiles` into either using 156 /// the matching `ParsedCompileCommand` or missing. 157 LimitFileRange limitFileRange(ParsedCompileCommand[] db, string[] onlyTheseFiles) { 158 import compile_db; 159 160 auto missing = appender!(string[])(); 161 auto app = appender!(ParsedCompileCommand[])(); 162 foreach (a; onlyTheseFiles.map!(a => tuple!("file", "result")(a, find(db, a)))) { 163 if (a.result.isNull) { 164 missing.put(a.file); 165 } else { 166 app.put(a.result.get); 167 } 168 } 169 170 return LimitFileRange(missing.data, app.data); 171 } 172 173 LimitFileRange limitOrAllRange(T)(ParsedCompileCommand[] db, T[] onlyTheseFiles) { 174 if (onlyTheseFiles.empty) 175 return LimitFileRange(null, db); 176 return limitFileRange(db, onlyTheseFiles.map!(a => cast(string) a).array); 177 } 178 179 /// Returns: prepend all CompileCommands parsed flags with `flags`. 180 auto prependFlags(RangeT)(RangeT r, string[] flags) 181 if (isInputRange!RangeT && is(ElementType!RangeT == ParsedCompileCommand)) { 182 return r.map!((a) { a.flags.prependCflags(flags); return a; }); 183 } 184 185 /** Find a best matching compile_command in the database against the path 186 * pattern `glob`. 187 * 188 * When searching for the compile command for a file, the compilation db can 189 * return several commands, as the file may have been compiled with different 190 * options in different parts of the project. 191 * 192 * Params: 193 * glob = glob pattern to find a matching file in the DB against 194 */ 195 Nullable!ParsedCompileCommand find(ParsedCompileCommand[] db, string glob) @safe { 196 import compile_db : isMatch; 197 198 foreach (a; db) { 199 if (isMatch(a.cmd, glob)) 200 return typeof(return)(a); 201 } 202 return typeof(return).init; 203 }